/**
 * @file sessiontests.cpp
 * @brief Seession class unit tests
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 3 of the License.
 *
 * @author VestniK (Sergey N.Vidyuk) sir.vestnik@gmail.com
 * @date 15 Jan 2010
 */
#include <QtCore/QUuid>
#include <QtTest/QtTest>

#include <QRemoteSignal>

#include "defaults.h"
#include "game.h"
#include "serverplayer.h"
#include "sessionfactory.h"
#include "sessionclient.h"
#include "gameclient.h"
#include "gameservice.h"
#include "playerclient.h"
#include "playerservice.h"

using namespace mjn;

class SessionTests: public QObject {
    Q_OBJECT
    private slots:
        void initTestCase() {
            mSessionClient = new qrs::SessionClient(&mClientManager);
            mGameClient = new qrs::GameClient(&mClientManager);
            mPlayerClient = new qrs::PlayerClient(&mClientManager);
            qRegisterMetaType<QUuid>("QUuid");
            qRegisterMetaType<mjn::TileID>("mjn::TileID");
            qRegisterMetaType< QList<mjn::TileID> >("QList<mjn::TileID>");
            qRegisterMetaType<mjn::WindType>("mjn::WindType");
            qRegisterMetaType< QList<QString> >("QList<QString>");
            qRegisterMetaType< QList<mjn::SessionInfo> >("QList<mjn::SessionInfo>");
        }

        void init() {
            mGame = new Game();
            mSessionFactory = new SessionFactory(mGame);
            mSession = mSessionFactory->create();
            connect(mSession->manager(),SIGNAL(send(QByteArray)),
                    &mClientManager,SLOT(receive(QByteArray)));
            connect(&mClientManager,SIGNAL(send(QByteArray)),
                    mSession->manager(),SLOT(receive(QByteArray)));
        }
        void cleanup() {
            delete mGame;
        }

        /**
         * Checks that services classes can be autoconnected to the classes of
         * the game imlementing reactions on those calls. This test is realy
         * imortant since it allows to be sure that all imcomming messages will
         * not only be processed but will cause necessary reaction in the
         * server application and all signals which should be delivered to the
         * client will cause sending message over the network.
         */
        void servecesAutoConnection() {
            qrs::GameService gameService;
            qrs::PlayerService playerService;
            Game game;
            ServerPlayer player(&game);
            QVERIFY2(gameService.autoconnect(&game),
                     "Failed to connect all signals and slots in GameService!");
            QVERIFY2(playerService.autoconnect(&player),
                     "Failed to connect all signals and slots in PlayerService!");
        }

        /**
         * @li when the session created the only service available is Session
         * service.
         * @li after session initialization request session initialized
         * response is sent and Player service becomes availanble.
         * @li also session gets unique identifier
         */
        void sessionInitialization() {
            QVERIFY(mSession->manager()->service("Session") != 0);
            QVERIFY(mSession->manager()->service("Player") == 0);
            QVERIFY( mSession->sid().isNull() );
            QSignalSpy spy(mSessionClient,SIGNAL(sessionStarted(QUuid)));
            mSessionClient->init();
            QCOMPARE(spy.count() , 1);
            QVERIFY(mSession->manager()->service("Player") != 0);
            QVERIFY( ! mSession->sid().isNull() );
        }

        /**
         * if session was deleted (because of client disconnected) it should be
         * possible to recover old session using old session id
         */
        void sessionRecovery() {
            QSignalSpy spy(mSessionClient,SIGNAL(sessionRestored()));
            mSessionClient->init();
            QUuid sid = mSession->sid();
            delete mSession;
            mSession = mSessionFactory->create();
            connect(mSession->manager(),SIGNAL(send(QByteArray)),
                    &mClientManager,SLOT(receive(QByteArray)));
            connect(&mClientManager,SIGNAL(send(QByteArray)),
                    mSession->manager(),SLOT(receive(QByteArray)));
            QCOMPARE(spy.count() , 0);
            mSessionClient->restore(sid);
            QCOMPARE(mSession->sid(), sid);
            QCOMPARE(spy.count() , 1);
        }

        /**
         * If client requests to recover session which is not exists it should
         * get noSuchSession signal.
         */
        void noSuchSessionSignal() {
            QSignalSpy spy1(mSessionClient,SIGNAL(sessionRestored()));
            QSignalSpy spy2(mSessionClient,SIGNAL(noSuchSession()));
            mSessionClient->init();
            QUuid sid = mSession->sid();
            delete mSession;
            mSession = mSessionFactory->create();
            connect(mSession->manager(),SIGNAL(send(QByteArray)),
                    &mClientManager,SLOT(receive(QByteArray)));
            connect(&mClientManager,SIGNAL(send(QByteArray)),
                    mSession->manager(),SLOT(receive(QByteArray)));
            QCOMPARE(spy1.count() , 0);
            QCOMPARE(spy2.count() , 0);
            mSessionClient->restore( QUuid::createUuid() );
            QVERIFY( mSession->sid().isNull() );
            QCOMPARE(spy1.count() , 0);
            QCOMPARE(spy2.count() , 1);
        }

        /**
         * if client tries to send session initialization request more then one
         * time. Error message should be sent in response.
         */
        void multiplyInitializationError() {
            QVERIFY( mSession->sid().isNull() );
            QSignalSpy spy1(mSessionClient,SIGNAL(sessionStarted(QUuid)));
            QSignalSpy spy2(mSessionClient,SIGNAL(sessionAlreadyStarted(QUuid)));
            mSessionClient->init();
            QCOMPARE(spy1.count() , 1);
            QCOMPARE(spy2.count() , 0);
            QUuid sid = mSession->sid();
            QVERIFY( ! sid.isNull() );
            mSessionClient->init();
            QCOMPARE(spy1.count() , 1);
            QCOMPARE(spy2.count() , 1);
            QCOMPARE( mSession->sid(), sid );
        }

        /**
         * if newlly connected client tries to recover session which was not
         * removed it should get error message. Session shouldn't be stolen.
         */
        void recoveryOfActiveSessionError() {
            QSignalSpy restoreSpy(mSessionClient,SIGNAL(sessionRestored()));
            QSignalSpy noSessionSpy(mSessionClient,SIGNAL(noSuchSession()));
            mSessionClient->init();
            QUuid sid = mSession->sid();
            qrs::ServicesManager clientManager;
            qrs::SessionClient *client = new qrs::SessionClient(&clientManager);
            Session *session = mSessionFactory->create();
            connect( session->manager(),SIGNAL(send(QByteArray)),
                     &clientManager,SLOT(receive(QByteArray)) );
            connect( &clientManager,SIGNAL(send(QByteArray)),
                     session->manager(),SLOT(receive(QByteArray)) );
            QCOMPARE(restoreSpy.count() , 0);
            QCOMPARE(noSessionSpy.count() , 0);
            mSessionClient->restore(sid);
            QVERIFY( session->sid().isNull() );
            QCOMPARE(restoreSpy.count() , 0);
            QCOMPARE(noSessionSpy.count() , 1);
        }

        /**
         * Checks that all clients receive startOfRound signal
         */
        void startOfRound() {
            qrs::ServicesManager manager[PLAYERS_COUNT];
            qrs::SessionClient *client[PLAYERS_COUNT];
            QSignalSpy *spy[PLAYERS_COUNT];
            for (int i = 0; i < PLAYERS_COUNT; i++) {
                // Initialization of client environment
                client[i] = new qrs::SessionClient(&manager[i]);
                qrs::GameClient *game = new qrs::GameClient(&manager[i]);
                qrs::PlayerClient *player = new qrs::PlayerClient(&manager[i]);
                Session *session = mSessionFactory->create();
                connect( session->manager(),SIGNAL(send(QByteArray)),
                         &manager[i],SLOT(receive(QByteArray)) );
                connect( &manager[i],SIGNAL(send(QByteArray)),
                         session->manager(),SLOT(receive(QByteArray)) );
                // Test
                spy[i] = new QSignalSpy(player,
                                        SIGNAL(startOfRound(mjn::WindType,
                                                            mjn::WindType,
                                                            QList<mjn::TileID>)));
                QSignalSpy sessionStarted(client[i],SIGNAL(sessionStarted(QUuid)));
                QCOMPARE(spy[i]->count() , 0);
                QCOMPARE(sessionStarted.count() , 0);
                client[i]->init();
                QCOMPARE(sessionStarted.count() , 1);
            }
            for (int i = 0; i < PLAYERS_COUNT; i++) {
                QCOMPARE(spy[i]->count() , 1);
                delete spy[i];
            }
        }

        void updateInfoSignal() {
            QSignalSpy spy(mSessionClient,SIGNAL(updateInfo(QList<mjn::SessionInfo>)));
            QCOMPARE(spy.count(), 0);
            mSessionClient->init();
            QCOMPARE(spy.count(), 1);
            mPlayerClient->setName("new name");
            QCOMPARE(spy.count(), 2);
        }

        // -------------- mjn::Game signals
        void tileDiscardedGameSignal() {
            mSessionClient->init();
            QSignalSpy spy( mGameClient,SIGNAL(tileDiscarded(mjn::TileID)) );
            QCOMPARE(spy.count() , 0);
            QMetaObject::invokeMethod(mGame,"tileDiscarded",Q_ARG(mjn::TileID,5));
            QCOMPARE(spy.count() , 1);
        }

        void roundFailedGameSignal() {
            mSessionClient->init();
            QSignalSpy spy( mGameClient,SIGNAL(roundFailed()) );
            QCOMPARE(spy.count() , 0);
            QMetaObject::invokeMethod(mGame,"roundFailed");
            QCOMPARE(spy.count() , 1);
        }

    private:
        qrs::ServicesManager mClientManager;
        qrs::SessionClient *mSessionClient;
        qrs::GameClient *mGameClient;
        qrs::PlayerClient *mPlayerClient;
        Game *mGame;
        SessionFactory *mSessionFactory;
        Session *mSession;
};

QTEST_MAIN(SessionTests);

#include "sessiontests.moc"
